/* 
 * cyg-profile.c - CygProfiler runtime functions.
 *
 * Michal Ludvig <michal@logix.cz>
 * http://www.logix.cz/michal/devel
 *
 * cyg-profile.c
 * - Compile your program with -finstrument-functions and link 
 *   together with this code.
 * - Logging is enabled as soon as your program calls
 *   cygprofile_enable() and disabled with cygprofile_disable().
 * - Before logging was enabled you can change the name 
 *   of a logfile by calling cygprofile_setfilename().
 */

/* Hint: -finstrument-functions, no_instrument_function */

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/syscall.h>

#include "cyg-profile.h"

//#define PROFILE_ALL

#define FN_SIZE 100
#define TH_SIZE 32
//#define FN_DEFAULT "/tmp/cyglog.%d"
#define FN_DEFAULT "./cyglog.%d"

/* Private variables.  */
static int level=0;
static FILE *logfile=NULL;
static int cyg_profile_enabled=0;
static char cyg_profile_filename[FN_SIZE+1];

static struct timeval tv;
static long long cyg_profile_basems=0;
static int cyg_profile_maxdepth=0;
static int cyg_profile_tid=0;

#ifdef PROFILE_ALL
static int tids[TH_SIZE];
static int tids_level[TH_SIZE];
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* Static functions. */
static void setbasetime (void)
	__attribute__ ((no_instrument_function));
static long long getdeltatime (void)
	__attribute__ ((no_instrument_function));
static FILE *openlogfile (const char *filename)
	__attribute__ ((no_instrument_function));
static void closelogfile (void)
	__attribute__ ((no_instrument_function));

/* Note that these are linked internally by the compiler. 
 * Don't call them directly! */
void __cyg_profile_func_enter (void *this_fn, void *call_site)
	__attribute__ ((no_instrument_function));
void __cyg_profile_func_exit (void *this_fn, void *call_site)
	__attribute__ ((no_instrument_function));

#ifdef __cplusplus
};
#endif

#ifdef PROFILE_ALL
static int find_tid_index_in_tids (int tid)
	__attribute__ ((no_instrument_function));

static int find_tid_index_in_tids(int tid)
{
    int i;

    for (i = 0; i < TH_SIZE; i++) {
        if (tids[i] == 0) {
            tids[i] = tid;
            break;
        }
        if (tids[i] == tid) {
            break;
        }
    }
    return i;
}

void
__cyg_profile_func_enter (void *this_fn, void *call_site)
{
    int midx = 0;
    int mtid = (int)syscall(__NR_gettid);
	if(cyg_profile_enabled)
		if(logfile || openlogfile(cyg_profile_filename)) {
            midx = find_tid_index_in_tids(mtid);
            if(midx < TH_SIZE) {
                if(tids_level[midx] < cyg_profile_maxdepth)
			        fprintf(logfile, "+ %d %d %p %p %lldus\n", mtid, tids_level[midx],
				        this_fn, call_site, getdeltatime());
                tids_level[midx]++;
            }
        }
}

void
__cyg_profile_func_exit (void *this_fn, void *call_site)
{
    int midx = 0;
    int mtid = (int)syscall(__NR_gettid);
	if(cyg_profile_enabled)
		if (logfile || openlogfile(cyg_profile_filename)) {
            midx = find_tid_index_in_tids(mtid);
            if(midx < TH_SIZE) {
                tids_level[midx]--;
                if(tids_level[midx] < cyg_profile_maxdepth)
			        fprintf(logfile, "- %d %d %p %p %lldus\n", mtid, tids_level[midx],
				        this_fn, call_site, getdeltatime());
            }
        }
}

#else

void
__cyg_profile_func_enter (void *this_fn, void *call_site)
{
	if(cyg_profile_enabled)
		if(logfile || openlogfile(cyg_profile_filename)) {
            if((int)syscall(__NR_gettid) == cyg_profile_tid) {
                if(level < cyg_profile_maxdepth)
			        fprintf(logfile, "+ %d %p %p %lldus\n", level,
				        this_fn, call_site, getdeltatime());
                level++;
            }
        }
}

void
__cyg_profile_func_exit (void *this_fn, void *call_site)
{
	if(cyg_profile_enabled)
		if (logfile || openlogfile(cyg_profile_filename)) {
            if((int)syscall(__NR_gettid) == cyg_profile_tid) {
                level--;
                if(level < cyg_profile_maxdepth)
			        fprintf(logfile, "- %d %p %p %lldus\n", level,
				        this_fn, call_site, getdeltatime());
            }
        }
}

#endif

void
cygprofile_enable (int maxdepth)
{
	if (!cyg_profile_filename[0])
		cygprofile_setfilename (FN_DEFAULT);
	if (!openlogfile (cyg_profile_filename))
		return;
	cyg_profile_enabled = 1;
    cyg_profile_maxdepth = maxdepth;
    cyg_profile_tid = (int)syscall(__NR_gettid);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
	if (logfile || openlogfile(cyg_profile_filename)) {
		fprintf(logfile, "cygprofile_enable basetime %lldus tid %d depth %d\n",
            getdeltatime(), cyg_profile_tid, cyg_profile_maxdepth);
    }
	setbasetime();
}

void
cygprofile_disable (void)
{
	cyg_profile_enabled = 0;
	cyg_profile_maxdepth = 0;
}

int
cygprofile_isenabled (void)
{ return cyg_profile_enabled; }

int 
cygprofile_setfilename (const char *filename)
{
	char *ptr;

	if (cygprofile_isenabled ())
		return -1;

	if (strlen (filename) > FN_SIZE)
		return -2;

	ptr = strstr (filename, "%d");
	if (ptr)
	{
		size_t len;
		len = ptr - filename;
		snprintf (cyg_profile_filename, len+1, "%s", filename);
		snprintf (&cyg_profile_filename[len], FN_SIZE - len, 
			"%d", getpid ());
		len = strlen (cyg_profile_filename);
		snprintf (&cyg_profile_filename[len], FN_SIZE - len,
			"%s", ptr + 2);
	}
	else
		snprintf (cyg_profile_filename, FN_SIZE, "%s", filename);

	if (logfile)
		closelogfile ();

	return 0;
}

char *
cygprofile_getfilename (void)
{
	if (!cyg_profile_filename[0])
		cygprofile_setfilename (FN_DEFAULT);
	return cyg_profile_filename;
}

static FILE *
openlogfile (const char *filename)
{
	static int complained = 0;
	FILE *file;
	
	if (complained)
		return NULL;
	
	if (logfile)
		return logfile;

	file = fopen(filename, "w");
	if (!file)
	{
		fprintf (stderr, "WARNING: Can't open logfile '%s': %s\n", 
			filename, strerror (errno));
		complained = 1;
		return NULL;
	}
	
	setlinebuf (file);
	logfile = file;

	return file;
}

static void
setbasetime (void)
{
    gettimeofday(&tv, NULL);
	cyg_profile_basems =  ((long long)tv.tv_sec * 1000000 + tv.tv_usec / 1);
	return;
}

static long long
getdeltatime (void)
{
    gettimeofday(&tv, NULL);
	return ((long long)tv.tv_sec * 1000000 + tv.tv_usec / 1) - cyg_profile_basems;
}

static void
closelogfile (void)
{
	if (logfile)
		fclose (logfile);
}

/*
vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
 */
